use clap::{command, Args, Subcommand};
use pngme_core::{chunk::Chunk, chunk_type::ChunkType, png::Png, result::Result};
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;

#[derive(Subcommand)]
#[command()]
pub enum Command {
    ///encode
    #[command(name = "encode")]
    Encode(EncodeArgs),
    ///decode
    Decode(DecodeArgs),
    Remove(RemoveArgs),
    Print {
        #[arg(short = 'f', long)]
        file: String,
    },
}
#[derive(Args)]
#[command()]
pub struct EncodeArgs {
    #[arg(short = 'f', long)]
    pub file: String,
    #[arg(short = 't', long)]
    pub chunk_type: String,
    #[arg(short = 'm', long)]
    pub message: String,
    #[arg(short = 'o', long)]
    pub output_file: Option<PathBuf>,
}

impl EncodeArgs {
    pub async  fn encode(&self) -> Result<()> {
        let mut output_file = PathBuf::new();
        match &self.output_file {
            Some(path) => {
                if path.is_dir() {
                    return Err("output file can't be an directory".into());
                } else {
                    output_file.push(path);
                }
            }
            None => {
                if is_native_file(&self.file){
                    output_file.push(&self.file);
                }else {
                    return  Err("please input the output file".into());
                }
            }
        }
        if self.chunk_type.chars().count() != 4 {
            return Err(("chunk type must be 4 character").into());
        }
        let fs_data = load_file(&self.file).await?;
        let mut png = Png::try_from(fs_data.as_slice())?;
        let chunk_type = ChunkType::from_str(self.chunk_type.as_ref())?;
        let encode_chunk = Chunk::new(chunk_type, self.message.clone().into_bytes());
        png.append_chunk(encode_chunk);
        fs::write(output_file, png.as_bytes())?;
        Ok(())
    }
}

#[derive(Args)]
#[command()]
pub struct DecodeArgs {
    #[arg(short = 'f', long)]
    pub file: String,
    #[arg(short = 't', long)]
    pub chunk_type: String,
}

impl DecodeArgs {
    pub async  fn decode(&self) -> Result<()> {
        if self.chunk_type.chars().count() != 4 {
            return Err(("chunk type must be 4 character").into());
        }
        let fs_data = load_file(&self.file).await?;
        let png = Png::try_from(fs_data.as_slice())?;
        match png.chunk_by_type(&self.chunk_type) {
            Some(chunk) => {
                println!("{}", chunk);
            }
            None => println!("not found that chunk type"),
        }
        Ok(())
    }
}

#[derive(Args)]
#[command()]
pub struct RemoveArgs {
    #[arg(short = 'f', long)]
    pub file: String,
    #[arg(short = 't', long)]
    pub chunk_type: String,
}

impl RemoveArgs {
    pub async  fn remove(&self) -> Result<()> {
        
        let fs_data = load_file(&self.file).await?;
        let mut png = Png::try_from(fs_data.as_slice())?;
        png.remove_first_chunk(&self.chunk_type)?;
        if is_native_file(&self.file){
            fs::write(&self.file, png.as_bytes())?;
        }
       
        println!("remove successfully");
        Ok(())
    }
}

pub async fn print(file: String) -> Result<()> {
    let fs_data = load_file(file).await?;
    let png = Png::try_from(fs_data.as_slice())?;
    println!("{}", png);
    Ok(())
}

async fn load_file(file_path: impl AsRef<str>) -> Result<Vec<u8>> {
    let result;
    let file_path=file_path.as_ref();
    if is_native_file(file_path) {
        result = reqwest::get(file_path).await?.bytes().await?.into();
    } else {
        let native_file_path = Path::new(&file_path);
        if !native_file_path.exists() {
            return Err(format!("{} is not exist!", &file_path).as_str().into());
        } else {
            result = fs::read(native_file_path)?;
        }
    }
    Ok(result)
}

fn is_native_file(file:impl AsRef<str>)->bool{
    let http_prefix = ["http://", "https://"];
    http_prefix.iter().any(|x| file.as_ref().starts_with(x))
}